package master

import (
	"context"
	"encoding/json"
	"time"

	"gitee.com/taotao_today/crontab/crontab/common"

	clientv3 "go.etcd.io/etcd/client/v3"
)

// JobMgr 任务管理器
type JobMgr struct {
	client *clientv3.Client
	kv     clientv3.KV
	lease  clientv3.Lease
	watch  clientv3.Watcher
}

var G_jobMgr *JobMgr

func InitJobMgr() (err error) {
	cli, err := clientv3.New(clientv3.Config{
		Endpoints:   G_config.EtcdEndPoints,
		DialTimeout: time.Duration(G_config.EtcdDialTimeout) * time.Millisecond,
	})
	if err != nil {
		return
	}

	G_jobMgr = &JobMgr{
		client: cli,
		kv:     clientv3.NewKV(cli),
		lease:  clientv3.NewLease(cli),
		watch:  clientv3.NewWatcher(cli),
	}
	return
}

// SaveJob 将任务保存到etcd中
func (m *JobMgr) SaveJob(job *common.Job) (oldJob *common.Job, err error) {
	// 把任务保存到/cron/jobs/任务名 -> json
	jobKey := common.JOB_SAVE_DIR + job.Name
	// 任务信息
	jobValue, err := json.Marshal(job)
	if err != nil {
		return
	}
	// 保存到etcd, 注意加上PrevKV参数
	putResp, err := m.client.KV.Put(context.TODO(), jobKey, string(jobValue), clientv3.WithPrevKV())
	if err != nil {
		return
	}

	// 如果是更新则返回旧的值
	var oldJobObj common.Job
	if putResp.PrevKv != nil {
		// 对旧值进行反序列化
		err = json.Unmarshal(putResp.PrevKv.Value, &oldJobObj)
		// 旧值非法不影响操作，新值已经put成功
		if err != nil {
			err = nil
			return
		}
		oldJob = &oldJobObj
	}
	return
}

// DeleteJob 从etcd中删除任务
func (m *JobMgr) DeleteJob(name string) (oldJob *common.Job, err error) {
	jobKey := common.JOB_SAVE_DIR + name

	// 从etcd中删除它
	deleteResp, err := m.client.Delete(context.TODO(), jobKey, clientv3.WithPrevKV())
	if err != nil {
		return nil, err
	}

	// 返回被删除的任务信息
	var oldJobObj common.Job
	if len(deleteResp.PrevKvs) > 0 {
		// 旧值非法不影响操作，已经delete成功
		if err = json.Unmarshal(deleteResp.PrevKvs[0].Value, &oldJobObj); err != nil {
			return nil, nil
		}
		oldJob = &oldJobObj
	}
	return
}

// ListJob 列举所有的任务
func (m *JobMgr) ListJob() (jobList []*common.Job, err error) {
	// 任务保存目录
	dirKey := common.JOB_SAVE_DIR

	// 从etcd中获取目录下所有任务信息
	getResp, err := m.kv.Get(context.TODO(), dirKey, clientv3.WithPrefix())
	if err != nil {
		return nil, err
	}

	// 初始化数组空间
	jobList = make([]*common.Job, 0)
	// len(jobList) == 0

	// 遍历所有任务，进行反序列化
	for _, kv := range getResp.Kvs {
		var jobObj common.Job
		err = json.Unmarshal(kv.Value, &jobObj)
		if err != nil {
			// 解析失败，忽略这个错误
			err = nil
			continue
		}
		jobList = append(jobList, &jobObj)
	}
	return
}

// KillJob 杀死任务, 给worker发送一个put通知
func (m *JobMgr) KillJob(name string) (err error) {
	// 通知worker杀死对应任务
	killerKey := common.JOB_KILLER_DIR + name

	// 让worker监听到一次put操作, 创建一个租约让其稍后自动过期
	leaseGrantResp, err := m.lease.Grant(context.TODO(), 1)
	if err != nil {
		return
	}

	// 租约ID
	leaseID := leaseGrantResp.ID

	// 设置killer标记
	_, err = m.kv.Put(context.TODO(), killerKey, "", clientv3.WithLease(leaseID))
	return
}
